﻿using System;
using System.Linq;
using JetBrains.Annotations;
using StreetPacMan.Server.Facade;
using StreetPacMan.Server.Rules;

namespace StreetPacMan.Server
{
    public interface IGameManagementHandler
    {
        ClientGame[] GetAllGames();
        Game CreateGame(string gameName);
        void DeleteGame(string gameName);

        Player JoinGame(string gameName, string entityName, PlayerRole role);

        Event[] GetAllEvents(int gameId);
        void SendGameMessage(SendGameMessageParameters gameStateParameters);
    }

    [UsedByIoc]
    public class GameManagementHandler : IGameManagementHandler
    {
        private readonly IConnectionProvider _ConnectionProvider;
        private readonly IInitialAppleLocationsProvider _InitialAppleLocationsProvider;
        private readonly IAddingPlayersRules[] _AddingPlayersRules;


        public GameManagementHandler(
            IInitialAppleLocationsProvider initialAppleLocationsProvider, 
            IConnectionProvider connectionProvider)
        {
            _InitialAppleLocationsProvider = initialAppleLocationsProvider;
            _ConnectionProvider = connectionProvider;

            _AddingPlayersRules = new IAddingPlayersRules[]
                                      {
                                          new GameMustExistRule(),
                                          new GameMustNotHaveStarted(),
                                          new PlayerNameIsNotEmpty(),
                                          new NoPlayerWithExistingName(),
                                          new OnlyThreePacMenAllowedRule(),
                                          new OnlyEightGhostsAllowedRule()
                                      };
        }

        public ClientGame[] GetAllGames()
        {
            using (var dbConnection = _ConnectionProvider.GetConnection())
            using (var pacManDataContext = new PacManDataContext(dbConnection))
            {
                return pacManDataContext
                    .Games
                    .Where(x => x.State != (int) GameState.GameOver)
                    .Select(x => new ClientGame(
                        x.Id,
                        x.Name, x.StateEnum,
                                       x.Players
                                      // .Where(y => y.Role != (int) PlayerRole.Observer)
                                       .Select(y => new GamePlayer(y.Id, y.Name, y.RoleEnum, y.StateEnum))
                                       .ToArray()))
                    .ToArray();
            }
        }

        public Game CreateGame(string gameName)
        {
            if (string.IsNullOrEmpty(gameName))
            {
                throw new ArgumentException("Game name should be non-empty");
            }

            return CreateGame(gameName, _InitialAppleLocationsProvider.GetInitialApples());
        }

        private Game CreateGame(string gameName, InitialApple[] initialApples)
        {
            using (var dbConnection = _ConnectionProvider.GetConnection())
            using (var pacManDataContext = new PacManDataContext(dbConnection))
            {
                var gameExists = pacManDataContext.Games.Any(x => x.Name == gameName);
                if (gameExists)
                {
                    throw new ArgumentException(string.Format("Game '{0}' already exists", gameName));
                }

                var game = new Game
                {
                    Name = gameName,
                    Score = 0,
                    StateEnum = GameState.Created,
                    StateChangeTime = DateTime.Now,
                };

                pacManDataContext.Games.InsertOnSubmit(game);

                var apples = initialApples
                    .Select(x => new Apple
                                     {
                                         Game = game,
                                         GeoLat = x.Lat,
                                         GeoLon = x.Lon,
                                         Kind = (int) x.Type,
                                         State = (int) AppleState.Active
                                     })
                    .ToArray();
                pacManDataContext.Apples.InsertAllOnSubmit(apples);

                var defaultObserverPlayer = new Player
                {
                    Game = game,
                    LastCommunicationAt = DateTime.Now,
                    RoleEnum = PlayerRole.Observer,
                    Name = "GameCreator",
                };

                pacManDataContext.Players.InsertOnSubmit(defaultObserverPlayer);

                pacManDataContext.AddGameEvent(EventType.GameCreated, string.Format("Game {0} was created", gameName), defaultObserverPlayer, game);

                pacManDataContext.SubmitChanges();
                return game;
            }
        }

        public Player JoinGame(string gameName, string entityName, PlayerRole role)
        {
            using (var dbConnection = _ConnectionProvider.GetConnection())
            using (var pacManDataContext = new PacManDataContext(dbConnection))
            {
                foreach (var addingPlayersRulese in _AddingPlayersRules)
                {
                    addingPlayersRulese.ThrowErrorIfInvalid(pacManDataContext, gameName, entityName, role);
                }

                var game = pacManDataContext.Games.Single(x => x.Name == gameName);

                if (game.StateEnum == GameState.Created)
                {
                    game.StateEnum = GameState.WaitingForMorePlayersToJoin;
                    game.StateChangeTime = DateTime.Now;
                }

                var entity = new Player
                {
                    GameId = game.Id,
                    RoleEnum = role,
                    Name = entityName,
                    StateEnum = PlayerState.JoinedDisabled,
                    LastCommunicationAt = DateTime.Now,
                };
                pacManDataContext.Players.InsertOnSubmit(entity);

                pacManDataContext.AddGameEvent(EventType.PlayerJoined, string.Format("{0} joined as {1}", entityName, role), entity,game);

                pacManDataContext.SubmitChanges();
                return entity;
            }
        }

        public Event[] GetAllEvents(int gameId)
        {
            using (var dbConnection = _ConnectionProvider.GetConnection())
            using (var pacManDataContext = new PacManDataContext(dbConnection))
            {
                return pacManDataContext.Events.Where(x => x.GameId == gameId).ToArray();
            }
        }

        public void SendGameMessage(SendGameMessageParameters gameStateParameters)
        {
            using (var dbConnection = _ConnectionProvider.GetConnection())
            using (var pacManDataContext = new PacManDataContext(dbConnection))
            {
                var sender = pacManDataContext.Players.Where(x => x.Id == gameStateParameters.SenderId).FirstOrDefault();
                if(sender==null)
                {
                    throw new ArgumentException(string.Format("Player id {0} does not exist", gameStateParameters.SenderId));
                }

                pacManDataContext.AddPlayerMessage(gameStateParameters.MessageText, sender);

                pacManDataContext.SubmitChanges();
            }
        }

        public void DeleteGame(string gameName)
        {
            using (var dbConnection = _ConnectionProvider.GetConnection())
            using (var pacManDataContext = new PacManDataContext(dbConnection))
            {
                var gameToDelete = pacManDataContext.Games.Where(x => x.Name == gameName).FirstOrDefault();
                if(gameToDelete==null)
                {
                    throw new InvalidOperationException(string.Format("The game '{0}' does not exist", gameName));
                }

                pacManDataContext.Games.DeleteOnSubmit(gameToDelete);
                pacManDataContext.Players.DeleteAllOnSubmit(gameToDelete.Players);
                pacManDataContext.Apples.DeleteAllOnSubmit(gameToDelete.Apples);
                pacManDataContext.Events.DeleteAllOnSubmit(gameToDelete.Events);
                pacManDataContext.Messages.DeleteAllOnSubmit(gameToDelete.Messages);
                pacManDataContext.SubmitChanges();
            }
        }
    }
}